 package littlecyberwar.model;

import java.util.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.awt.*;

import littlecyberwar.ui.*;
import littlecyberwar.util.*;
import littlecyberwar.tool.*;
import littlecyberwar.control.*;
import ModuleBuilder.model.*;

/**
 * This is the generic object that all commands to modify the model "flows" through.
 * One can not directly modify the model w/o flowing through here.
 *
 *
 * An incoming command will basically go through here. get stored, and also get passed on to the
 * Model.
 *
 * A file Backing store is also connected to this Facade, so any incoming command also flows out to them.
 * 1. default (do nothing)
 * 2. file (store all incoming commands to a file)
 *
 * This the the last stop in a pipe of cmd facades that modifies the model.
 * This class does NOT have to worry about the networkings of the program.
 *
 */
public class ModelChangeFacade implements iModelFacade {
  
  	private PlayingBoardModel model;
	private CmdBackingStore filebackingStore; 
	private ChatPanel chatPanel;
	private ProfileCardPanel profileCardPanel=null;
  	private static ModelChangeFacade instance;
  	private PlayingBoardPanel boardPanel;
  	private String distanceUnit;
  	
  	public ModelChangeFacade( PlayingBoardModel modelIn ) {
    	model = modelIn;
		instance = this;
   	}
   	
   	public void setBoardPanel(PlayingBoardPanel valIn){
   		boardPanel= valIn;
   	}
   	
   	public static ModelChangeFacade getInstance(){
   		return ( instance );
   	}

	public void setChatPanel (ChatPanel in) {
		chatPanel = in; 	
	}
  /**
   * Setup a new command backing store that connects through this Dacade
   * The facade can have multiple number of backingstore objects
   */
  public void addBackingStore( CmdBackingStore bs ) {
	filebackingStore = bs;
  }
  
  public void deleteBackingStore (CmdBackingStore bs) {
	filebackingStore=null;
  }
  
  public void addCmd( Command cmd ) {
  	if ( filebackingStore != null ){
		filebackingStore.add(cmd);
  	}	
	changeModel(cmd);
  }
  /**
   * This version is used for external commands (no need to keep track of old
   * position.
   *
   * SUBCLASS NEED TO OVERRIDE THIS
   */

  public ArrayList getAllElements() {
    return ( model.getAllElements() );
  }

  public void modelChanged() {
    model.modelChanged();
  }

//  public void modelChanged(Rectangle r) {
//     model.modelChanged(r);
//  }

   public void addSetElement( Element e ) {
     model.addSetElement(e);
  }

  public void addElement( Element e ) {
	model.addElement(e);
 }
  /**
   * Apply the change to the model
   */
  protected void changeModel ( Command cmd ) {
  	// cmd might be null if online game gets disconnected
  	if ( cmd != null )	{	  	
	  	int cmdType= cmd.getType();
	    if ( cmdType == Command._commandType_unit) {
	    	handleUnitCommand((UnitCommand)cmd);
	    } else if ( cmdType == Command._commandType_chat ){
	    	handleChatCommand((ChatCommand)cmd);
	    } else if (cmdType == Command._commandType_placement) {
	    	handlePlacementCommand((UnitCommandPlacement)cmd);
	  	} else if (cmdType == Command._commandType_dieroll) {
	  		handleDiceCommand((DiceCommand)cmd); 
	  	} else if (cmdType == Command._commandType_attribChange) {
			handleAttribChangeCommand((UnitAttributeCommand)cmd);
	    } else if (cmdType == Command._commandType_remove) {
	    	handleRemoveCommand((RemoveCommand)cmd);
	    } else if (cmdType == Command._commandType_gameState) {
			handleGameStateCommand((GameStateCommand)cmd);		
	    } else if (cmdType == Command._commandType_unit_rotate) {
	    	handleUnitCommand((UnitCommand)cmd);
		} else if (cmdType == Command._commandType_changeLayer) {
	    	handleChangeLayerCommand((ChangeLayerCommand)cmd);
		}

  	}  	
  }

	/**
	 * From the command object:
	 * 1. find the unit element from the unitid through the ElementIdDictionary
	 * 2. Apply the element's new location to the element.
	 * 3. Apply the message of the cmd. 
	 * 3. signal that the model has changed
	 * 4. call into the current handler in case it's a watch
	 * 
	 * @param cmd the Unit command that's to be applied to the model
	 */
	private void handleUnitCommand( UnitCommand cmd ) {
		if ( cmd instanceof MultiUnitCommand ) {
			ArrayList allCmds = ((MultiUnitCommand)cmd).getAllCmd();
			int size = allCmds.size();
			ArrayList allElements = new ArrayList(size);
			for ( int i = 0; i < size; i++ ){
				UnitCommand indCmd = (UnitCommand)allCmds.get(i);
				if ( indCmd.getType()  == Command._commandType_unit) {
					handleIndividualUnitCmd( indCmd, false, allElements );	
				} else {
					// must be rotate command
					handleIndividualUnitRotateCmd( (UnitRotateCommand)indCmd, false, allElements );
				}
				
			}
			MouseHandler currHandler = boardPanel.getCurrHandler();
			currHandler.applyRemoteChange(allElements);
			model.modelChanged();
			
		} else {
			if ( cmd.getType()  == Command._commandType_unit) {
				handleIndividualUnitCmd( cmd, true, null );
			} else {
				// must be rotate command
				handleIndividualUnitRotateCmd( (UnitRotateCommand)cmd, true, null );				
			}
			model.modelChanged();
		}
	}
	
	/**
	 * 
	 * @param cmd
	 * @param applyRemoteChange
	 * @param allElements- an in/out param where the routine is to stuff in the element
	 */
	private void handleIndividualUnitCmd( UnitCommand cmd, boolean applyRemoteChange, ArrayList allElements ) {
		Integer unitId = cmd.getUnitId();
		Integer actionId = cmd.getActionId();
		AffineTransform trans = cmd.getNewLocation();
		
		Element el = ElementIdDictionary.get(unitId);
		synchronized ( el ){
			el.setTransform((AffineTransform)trans.clone());
		}
		if ( allElements != null ) {
			allElements.add(el);
		}
		// Debug.out("At handleUnitCmd: trans=" + trans.toString());
		String unitName = el.getName();
		String actionString = (String)MouseHandler.actionStringLookup.get(actionId);
		String distanceStr = cmd.getMoveDistanceString();
		chatPanel.addGameMsg(unitName + actionString + distanceStr + " " + distanceUnit, cmd.getPlayerName());
		
		// need to update the unit's distance moved field
		if ( profileCardPanel == null ){
			profileCardPanel = ProfileCardPanel.getInstance();
		}
		UnitModel unit = el.getUnit();
		String attribName = Util._elementAttribDistanceMoved;
		Double d = (Double)unit.getHashValue(attribName);
		Double val = new Double (d.doubleValue() + Double.valueOf(distanceStr).doubleValue());
		unit.addHashValue(attribName, val);
		// Debug.out("profileCardPanel= " + profileCardPanel.toString());
		profileCardPanel.setUnit(unit);
		profileCardPanel.UnitDistanceAttributeUpdated(unit.getId(), attribName, val);
		
		if ( applyRemoteChange == true ){
			MouseHandler currHandler = boardPanel.getCurrHandler();
			currHandler.applyRemoteChange(el);
		}		
		return;
	}	
	
	private void handleIndividualUnitRotateCmd( UnitRotateCommand cmd, boolean applyRemoteChange, ArrayList allElements ) {
		Integer unitId = cmd.getUnitId();
		Integer actionId = cmd.getActionId();
		AffineTransform trans = cmd.getNewLocation();
		
		Element el = ElementIdDictionary.get(unitId);
		synchronized ( el ){
			el.setTransform((AffineTransform)trans.clone());
		}
		if ( allElements != null ) {
			allElements.add(el);
		}
		// Debug.out("At handleUnitCmd: trans=" + trans.toString());
		String unitName = el.getName();
		String actionString = (String)MouseHandler.actionStringLookup.get(actionId);
		String distanceStr = cmd.getMoveDistanceString();

		// the degree = angular distance / (radius / scale)
		double angularDistance = cmd.getMoveDistance();
		double radius = cmd.getRotateRadius() / Util.getMapScale();
		String degreeStr = MouseHandler.df.format(Math.toDegrees(angularDistance/ radius));

		chatPanel.addGameMsg(unitName + actionString + distanceStr + " " + distanceUnit + " (" + degreeStr + " degrees.)", cmd.getPlayerName());
		
		// need to update the unit's distance moved field
		if ( profileCardPanel == null ){
			profileCardPanel = ProfileCardPanel.getInstance();
		}
		UnitModel unit = el.getUnit();
		String attribName = Util._elementAttribDistanceMoved;
		Double d = (Double)unit.getHashValue(attribName);
		Double val = new Double (d.doubleValue() + Double.valueOf(distanceStr).doubleValue());
		unit.addHashValue(attribName, val);
		// Debug.out("profileCardPanel= " + profileCardPanel.toString());
		profileCardPanel.setUnit(unit);
		profileCardPanel.UnitDistanceAttributeUpdated(unit.getId(), attribName, val);
		
		if ( applyRemoteChange == true ){
			MouseHandler currHandler = boardPanel.getCurrHandler();
			currHandler.applyRemoteChange(el);
		}		
		return;
	}
	
	private void handlePlacementCommand (UnitCommandPlacement cmd) {
		Integer unitId = cmd.getUnitId();
		Integer actionId = cmd.getActionId();
		AffineTransform trans = cmd.getNewLocation();
		Integer trayId = cmd.getTrayId();
		Tray t = Tray.get(trayId);
		Vector v = t.getSubTrayFromUnitId( unitId ); 
		UnitTrayView utv = t.getUnitTrayViewFromElementId( unitId, v );
		synchronized(t){
			t.removeSelectElement( v, utv ); 
		}

		Element el = ElementIdDictionary.get(unitId);
		model.addSetElement( el );
		el.setTransform(trans);
		// Debug.out(this.toString()+ ": handlePlacementCommand");
		chatPanel.addGameMsg(el.getName() + " has been placed on board.", cmd.getPlayerName());
		MouseHandler currHandler = boardPanel.getCurrHandler();
		currHandler.applyRemoteChange(el);
		model.modelChanged();
		el.setOnboard(true);			
	}
	
	private void handleRemoveCommand(RemoveCommand cmd){
		String trayName = cmd.trayName;
		String subTrayName= cmd.subTrayName;
		ArrayList elementIds= cmd.elementIds;
		
		Tray t = Tray.getFromName(trayName);
		Vector v = t.getSubTrayFromName( subTrayName );
		
		// now loop through all the elements, and then put each one of them into the tray
		PlayingBoardModel model = (PlayingBoardModel)Util.lookup.get(Util._PLAYING_BOARD_MODEL);
		MouseHandler currHandler = boardPanel.getCurrHandler();
		
		for ( int i=0; i < elementIds.size(); i++ ){
			Integer id = (Integer)elementIds.get(i);
			Element el = ElementIdDictionary.get(id);
			
			currHandler.resetSelectedElement( el );
			model.removeElement( el );
			
			// now add the element to the tray			
			UnitTrayView trayVCmdr = new UnitTrayView( el.getName(), el );
			t.addSelectElement(v, trayVCmdr);
		}
		model.modelChanged();
		return;
	}
	
	private void handleChangeLayerCommand(ChangeLayerCommand cmd){
		int dest = cmd.layerId;
		boolean destEnabled = LayeredUnitHandler.isLayerEnabled(dest);
		ArrayList elementIds= cmd.elementIds;
		
		// now loop through all the elements, and then put each one of them into the right layer
		MouseHandler currHandler = boardPanel.getCurrHandler();		
		
		for ( int i=0; i < elementIds.size(); i++ ){
			Integer id = (Integer)elementIds.get(i);
			Element el = ElementIdDictionary.get(id);
			
			int src = ((Integer)el.getNewProps(Util._layer)).intValue();
			LayeredUnitHandler.moveUnitsBetweenLayers(el, src, dest);
			
			// now check if this element is now in an hidden layer, if so, deselect it.
			if ( !destEnabled ){
				currHandler.resetSelectedElement( el );
			}
		}
		model.modelChanged();
		return;
	}
	
	private void handleChatCommand( ChatCommand cmd ) {
		String text=cmd.getMsgContent();
		chatPanel.addChatMsg(text, cmd.getPlayerName());	
	}
	
	private void handleDiceCommand(DiceCommand cmd ) {
		String text = cmd.getResultStr();
		chatPanel.addGameMsg(text, cmd.getPlayerName());
	}
	
	private void handleGameStateCommand(GameStateCommand cmd) {
		if ( cmd.subCmd == GameStateCommand._SUBCMD_RESET_ALL_MOV_DISTANCE){
			GameStateControl.resetAllDistanceMoved(profileCardPanel);
		}
	}
	
	private void handleAttribChangeCommand( UnitAttributeCommand cmd ){
		if ( profileCardPanel == null ){
			profileCardPanel = ProfileCardPanel.getInstance();
		}
		Integer unitId = cmd.getUnitId();
		Integer val = cmd.getValue();
		String attribName = cmd.getAttributeName();
		Element el = ElementIdDictionary.get(unitId);
		UnitModel unit = el.getUnit();
		unit.addHashValue(attribName, val);
		profileCardPanel.UnitAttributeUpdated(unitId, attribName, val);
		String unitName = el.getName();
		String text = unitName + " changed attribute: " + attribName + "= " + val;
		MouseHandler currHandler = boardPanel.getCurrHandler();
		currHandler.applyRemoteChange(el);

		chatPanel.addGameMsg(text, cmd.getPlayerName());	
	}
	/**
	 * @param distanceUnit The distanceUnit to set.
	 */
	public void setDistanceUnit(String distanceUnit) {
		this.distanceUnit = distanceUnit;
	}
}